//
//  Projector.m
//  ___PROJECTNAME___
//
//  Created by Goffredo Marocchi on 8/26/10.
//  Copyright 2010 unofficial_sio2_template. All rights reserved.
//

#import "Projector.h"
#import "EAGLView.h"
#import "TextureMatrix.h"

extern id sio2GLView;

extern BOOL backprojectionFix;
extern BOOL projectorEnable;

@interface Projector ()

-(void) loadTexture:(NSString *)file baseTexture:(BOOL)flag;
-(void) loadProjectorTex;
-(void) loadBackProjectionFixTex;

@end

@implementation Projector

@synthesize aspectRatio, updateTextureMap;

-(id) init {
	if (self = [super init]) {
		GLint backingWidth;
		GLint backingHeight;
		
		glGenTextures(1, &texture);
		glGenTextures(1, &backprojectionFixTexture);
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
		glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
		
		aspectRatio = 1.0f; //we are not using an iPad shaped projector ;).
		updateTextureMap = TRUE;
		
	}
	return self;
}

-(void) dealloc {
	SAFE_FREE(spriteData);
	SAFE_FREE(spriteDataFix);
	glDeleteTextures(1, &texture);
	glDeleteTextures(1, &backprojectionFixTexture);
	
	[super dealloc];
}

-(void) setProjector:(NSString*)sio2Projector{
	projector = ( SIO2camera * )sio2ResourceGet( sio2->_SIO2resource,
												SIO2_CAMERA, 
												(char*)[sio2Projector UTF8String]);
	PRINT_SVEC ("projector's origin = ", *projector->_SIO2transform->loc);
}

-(void) calculateProjector {
	//Camera setup for landscape: 
	//order submitted: PROJECTION_MATRIX (Perspective) --> VIEW_MATRIX (lookAt) --> MODEL_MATRIX 
	//order of evaluation: modelToWorld --> worldToCamera (view) --> CameraToHomogeneous/Post-Projection (proj)
	
	//for texture projection: submit BIAS, Projection, Modelview 
	//(PROJECTION and MODELVIEW are calculated from the projector's point of view)
	
	MatrixIdentity(mBias);
	mBias.f[M44_CM(0,0)] = 0.5f;
	mBias.f[M44_CM(1,1)] = 0.5f;
	mBias.f[M44_CM(2,2)] = 0.5f;
	
	mBias.f[M44_CM(0,3)] = 0.5f;
	mBias.f[M44_CM(1,3)] = 0.5f;
	mBias.f[M44_CM(2,3)] = 0.5f;
	
	[sio2GLView matPerspective:&mProjection.f[0] withFov:projector->fov
				   aspectRatio:1.0f zNear:projector->cstart zFar:projector->cend 
				portrait:NO zRangeInverted:INVERTED_ZRANGE];
	
	[sio2GLView matLookAt:&mView eye:projector->_SIO2transform->loc 
					 vDir:projector->_SIO2transform->dir upVector:(vec3*)&upWorld];
	
	//T = Bias * ProjP * ViewP
	//Oolong's matrix functions operate in row major order
	MatrixIdentity(mTextureProjection);
	MatrixMultiply(mTextureProjection, mProjection, mBias);
	MatrixMultiply(mTextureProjection, mView, mTextureProjection);
	
	MATRIX mFix;
	
	float d = 0.0f;
	
	d = (-1.0f) * sio2DotProduct(projector->_SIO2transform->dir, projector->_SIO2transform->loc);
	
	CMLog (@" (N * P_plane) + D = %f", sio2DotProduct(projector->_SIO2transform->dir, projector->_SIO2transform->loc) + d);
	
	MatrixIdentity(mFix);
	mFix.f[M44_RM(1,1)]=0.0f;
	mFix.f[M44_RM(2,2)]=0.0f;
	
	mFix.f[M44_RM(0,0)]=projector->_SIO2transform->dir->x;
	mFix.f[M44_RM(1,0)]=projector->_SIO2transform->dir->y;
	mFix.f[M44_RM(2,0)]=projector->_SIO2transform->dir->z;
	mFix.f[M44_RM(3,0)]= d;
	
#if DEBUG_TEST==1
	{
		VECTOR4 vIn;
		VECTOR4 vOut;
		
		vIn.x =projector->_SIO2transform->loc->x;
		vIn.y =projector->_SIO2transform->loc->y;
		vIn.z =projector->_SIO2transform->loc->z;
		vIn.w =1.0f;
		
		MatrixVec4Multiply (vOut, vIn, mFix);
		CMLog(@"s = (BackProjectionFix_Matrix * V_Plane) = %f", vOut.x); //close enough to 0;
		
		vIn.x =1;
		vIn.y =1;
		vIn.z =1;
		vIn.w =1.0f;
		
		MatrixVec4Multiply (vOut, vIn, mFix);
		CMLog(@"s = (BackProjectionFix_Matrix * V(1,1,1)) = %f", vOut.x); 
		
		vIn.x =20;
		vIn.y =40;
		vIn.z =50;
		vIn.w =1.0f;
		
		MatrixVec4Multiply (vOut, vIn, mFix);
		CMLog(@"s = (BackProjectionFix_Matrix * V(20,40,50)) = %f", vOut.x); 
		
		vIn.x =-2;
		vIn.y =-4;
		vIn.z =-5;
		vIn.w =1.0f;
		
		MatrixVec4Multiply (vOut, vIn, mFix);
		CMLog(@"s = (BackProjectionFix_Matrix * V(-2,-4,-5)) = %f", vOut.x); 
	}
#endif
	
	//d +=0.01f;
	
	//T =  
	//Oolong's matrix functions operate in row major order
	MatrixIdentity(mBackProjectionFix);
	MatrixCopy(mFix, mBackProjectionFix);
	
	
}
-(void) submitProjectorGL {
	
	//glEnable(GL_BLEND);
	
	//glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
	
	BOOL fixActive = backprojectionFix;
	
	[self loadProjectorTex];
	
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Plane")];
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Plane_2")];
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Plane_3")];
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Cylinder")];
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Cylinder_2")];
	[self sendVPosAsTexcoords:(SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)"object/Cube")];
	
	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
	
	glActiveTexture(GL_TEXTURE0);
	glDisable(GL_TEXTURE_2D);
	
	if(fixActive){
		glActiveTexture(GL_TEXTURE1);
		glDisable(GL_TEXTURE_2D);
		glActiveTexture(GL_TEXTURE0);
	}
	//glDisable(GL_BLEND);
	
	[SysTools checkGLerrors];
}

-(void) loadTexture:(NSString *)file baseTexture:(BOOL)flag {
	CGContextRef spriteContext;
	GLubyte *spriteDataPTR;
	size_t*	widthPTR;
	size_t* heightPTR;
	
	if (flag) {
		spriteDataPTR = spriteData;
		widthPTR = &width;
		heightPTR = &height;
	}
	else {
		spriteDataPTR =  spriteDataFix;
		widthPTR = &widthFix;
		heightPTR = &heightFix;
	}
	
	// Creates a Core Graphics image from an image file
	spriteImage = [UIImage imageNamed:file].CGImage;
	
	if(spriteImage) {
		// Get the width and height of the image
		*widthPTR = CGImageGetWidth(spriteImage);
		*heightPTR = CGImageGetHeight(spriteImage);
		
		// Allocated memory needed for the bitmap context
		SAFE_FREE(spriteDataPTR);
		spriteDataPTR = (GLubyte *) calloc((*widthPTR) * (*heightPTR) * 4, sizeof(GLubyte));
		// Uses the bitmap creation function provided by the Core Graphics framework. 
		spriteContext = CGBitmapContextCreate(spriteDataPTR, *widthPTR, *heightPTR, 8, (*widthPTR) * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
		// After you create the context, you can draw the sprite image to the context.
		
		//y-axis flip (fix)
		CGContextTranslateCTM (spriteContext, 0, *heightPTR);
		CGContextScaleCTM (spriteContext, 1.0, -1.0);
		
		CGContextDrawImage(spriteContext, CGRectMake(0.0, 0.0, (CGFloat)*widthPTR, (CGFloat)*heightPTR), spriteImage);
		// You don't need the context at this point, so you need to release it to avoid memory leaks.
		CGContextRelease(spriteContext);
	}
	else NSLog(@"(!) File not found... (!)");
	
	// Texture dimensions must be a power of 2. If you write an application that allows users to supply an image,
	// you'll want to add code that checks the dimensions and takes appropriate action if they are not a power of 2.
	
	if (flag) spriteData = spriteDataPTR;
	else spriteDataFix = spriteDataPTR;
}

-(void) uploadProjectorTex:(NSString *)file {
	[self loadTexture:file baseTexture:YES];
	[self loadTexture:@"BackProjectionFix.png" baseTexture:NO];
}

-(void) loadProjectorTex {
	
	glActiveTexture(GL_TEXTURE0);
	glDeleteTextures(1, &texture);
	glGenTextures(1, &texture); 
	
	glBindTexture(GL_TEXTURE_2D, texture);	
	
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
	
	// Set the texture parameters to use a minifying filter and a linear filer (weighted average)
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	
	//TODO: set TexEnv appropriately in order for multitexturing to enable texture projection to be done in a single pass (additive)
	
	// Specify a 2D texture image, providing the a pointer to the image data in memory
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
	
	BOOL fixActive = backprojectionFix;
	if(fixActive) [self loadBackProjectionFixTex];
}

-(void) loadBackProjectionFixTex {
	glActiveTexture(GL_TEXTURE1);
	glDeleteTextures(1, &backprojectionFixTexture);
	glGenTextures(1, &backprojectionFixTexture); 
	glBindTexture(GL_TEXTURE_2D, backprojectionFixTexture);
	
	glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
	
	//Sample RGB, multiply by previous texunit result
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);   //Modulate RGB with RGB
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
	//Sample ALPHA, multiply by previous texunit result
	glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);  //Modulate ALPHA with ALPHA
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
	glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
	glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
	
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	
	// Specify a 2D texture image, providing the a pointer to the image data in memory
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, widthFix, heightFix, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteDataFix);
	
}


-(void) sendVPosAsTexcoords:(SIO2object*)obj{
	
	[SysTools checkGLerrors];
	vec3 plane = *projector->_SIO2transform->dir;
	BOOL fixActive = backprojectionFix;
	
	//glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	{
		obj->buf = (unsigned char *)sio2MapBuffer( obj->vbo, GL_ARRAY_BUFFER );
		
		//coordinates are in Object space... we need to transform them to World Space...
		glTranslatef(obj->_SIO2transform->loc->x, 
					 obj->_SIO2transform->loc->y, 
					 obj->_SIO2transform->loc->z);
		
		glVertexPointer(3, GL_FLOAT, 0, (void*) NULL);
		glEnableClientState(GL_VERTEX_ARRAY);
		
		//glNormalPointer( GL_FLOAT, 0, SIO2_BUFFER_OFFSET( obj->vbo_offset[ SIO2_OBJECT_NORMALS ] ) );
		//glEnableClientState(GL_NORMAL_ARRAY);
		
		glActiveTexture(GL_TEXTURE0);
		glEnable(GL_TEXTURE_2D);
		glMatrixMode(GL_TEXTURE);
		glPushMatrix();
		{
			glLoadMatrixf(mTextureProjection.f);
			
			glTranslatef(obj->_SIO2transform->loc->x, 
						 obj->_SIO2transform->loc->y, 
						 obj->_SIO2transform->loc->z);
			
			glTexCoordPointer(3, GL_FLOAT, 0, (void*) NULL);
			glEnableClientState(GL_TEXTURE_COORD_ARRAY);
			
			if (fixActive) {
				glActiveTexture(GL_TEXTURE1);
				glEnable(GL_TEXTURE_2D);
				glMatrixMode(GL_TEXTURE);
				glPushMatrix();
				{
					glLoadMatrixf(mBackProjectionFix.f);
					//glLoadMatrixf(mTextureProjection.f);
					
					glTranslatef(obj->_SIO2transform->loc->x, 
								 obj->_SIO2transform->loc->y, 
								 obj->_SIO2transform->loc->z);
					
					glTexCoordPointer(3, GL_FLOAT, 0, (void*) NULL);
					glEnableClientState(GL_TEXTURE_COORD_ARRAY);
				}
			}
			
			glMatrixMode(GL_MODELVIEW);
			{
				if (!obj) {CMLog (@"(!) error, pointer to SIO2 object is NULL(!)");return;}
				
				for (int i=0; i<obj->n_vgroup; i++) {
					obj->_SIO2vertexgroup[i]->ind = ( unsigned short *) sio2MapBuffer(obj->_SIO2vertexgroup[i]->vbo, 
																					  GL_ELEMENT_ARRAY_BUFFER);
#if DEBUG_TEST==1
					{
						DPRINTF("\n");
						
						int j=0;
						while (j < obj->_SIO2vertexgroup[i]->n_ind) {
							
							int idV = obj->_SIO2vertexgroup[ i ]->ind[j];
							vec3 vertex;
							
							memcpy( &vertex, &obj->buf[ idV * 12 ], 12 );
							
							VECTOR4 vIn;
							VECTOR4 vOut;
							
							vIn.x = vertex.x + obj->_SIO2transform->loc->x;
							vIn.y = vertex.y + obj->_SIO2transform->loc->y;
							vIn.z = vertex.z + obj->_SIO2transform->loc->z;
							vIn.w = 1.0f;
							
							MatrixVec4Multiply(vOut, vIn, mBackProjectionFix );
							
							DPRINTF("\n(mBackProjectionFix * vertex),\ns = %f, t = %f, r = %f, q = %f", 
									vOut.x, vOut.y, vOut.z, vOut.w);
							
							j++;
						}
					}
					
#endif
					
					glDrawElements( obj->_SIO2vertexgroup[i]->mode,
								   obj->_SIO2vertexgroup[i]->n_ind,
								   GL_UNSIGNED_SHORT,
								   (void *)NULL );
					
					obj->_SIO2vertexgroup[i]->ind = ( unsigned short *) sio2UnmapBuffer( obj->_SIO2vertexgroup[i]->vbo, 
																						GL_ELEMENT_ARRAY_BUFFER );   
				}
				obj->buf = (unsigned char *) sio2UnmapBuffer(obj->vbo, GL_ARRAY_BUFFER);
			}
		}
		if(fixActive) {
			glActiveTexture(GL_TEXTURE1);
			glDisableClientState(GL_TEXTURE_COORD_ARRAY);
			glMatrixMode(GL_TEXTURE);
			glPopMatrix();
		}
		
		glActiveTexture(GL_TEXTURE0);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glMatrixMode(GL_TEXTURE);
		glPopMatrix();
	}
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
	//glColor4f(0, 0, 0, 1);
	glDisableClientState(GL_VERTEX_ARRAY);
	//glDisableClientState(GL_NORMAL_ARRAY);
	
	SIO2_SOFT_RESET();
}

-(void) rotateZ:(float)angleDeg {
	sio2TransformRotateZ(projector->_SIO2transform, angleDeg);	
}

@end
